#ifndef XG_WEBX_STD_CPP
#define XG_WEBX_STD_CPP
///////////////////////////////////////////////////////////
#include "../std.h"
#include "../route.h"
#include <zlib/zlib.h>

#ifndef HTTP_GZIP_SIZE
#define HTTP_GZIP_SIZE  (10 * 1024)
#endif

#ifndef XG_UPLOADFILE_REQUEST_MAXSZ
#define XG_UPLOADFILE_REQUEST_MAXSZ		(128 * 1024 * 1024)
#endif

typedef int (*HttpPluginInitFunc)(IHttpServer*);

static int GetHttpStatus(int code)
{
	switch(code)
	{
		case XG_TIMEOUT: return 401;
		case XG_SYSBUSY: return 503;
		case XG_DAYLIMIT: return 503;
		case XG_PARAMERR: return 400;
		case XG_AUTHFAIL: return 403;
		default: return 500;
	}
}

string& HttpCgiSetup::GetCgiPath()
{
	static string path;

	return path;
}

int webx::ProcessBase::simpleResponse(int code)
{
	clearResponse();

	return out.printf("{\"code\":%d}", code).size();
}
string webx::ProcessBase::checkLogin(const string& sid)
{
	checkSession(sid);

	const string& user = token->getUser();

	if (user.empty()) throw Exception(XG_TIMEOUT);

	webx::CheckAccess(request, token->getGrouplist());

	return user;
}
void webx::ProcessBase::checkSession(const string& sid)
{
	auto check = [&](){
		string key = sid;

		if (key.empty())
		{
			key = webx::GetSessionId(request);

			if (key.empty()) return XG_TIMEOUT;
		}
	
		token = LoginToken::Get(key);

		return token && key == token->getSessionId() ? XG_OK : XG_TIMEOUT;
	};

	int res = check();

	if (res < 0) throw Exception(res, res == XG_TIMEOUT ? "session timeout" : "check session failed");
}
void webx::ProcessBase::parse(JsonReflect& data, bool decode)
{
	string content = request->getDataString();

	if (content.front() == '{' || content.back() == '}')
	{
		data.fromString(decode ? stdx::DecodeURL(content) : content);
	}
	else
	{
		vector<ReflectItem> vec = ReflectHelper::GetAttrList(&data);

		for (ReflectItem& item : vec)
		{
			string val = request->getParameter(item.getName());

			item.check(val);
			item.set(&data, val);
		}
	}
}
void webx::ProcessBase::checkSystemRight(const string& grouplist)
{
	string group = grouplist;

	if (group.empty() && token) group = token->getGrouplist();

	if (group.empty()) throw Exception(XG_AUTHFAIL);

	group = "," + group + ",";

	if (group.find(",system,") == string::npos) return throw Exception(XG_AUTHFAIL);
}
int webx::ProcessBase::process()
{
	return XG_FAIL;
}
int webx::ProcessBase::forward(ProcessBase* cgi)
{
	cgi->request = request;
	cgi->response = response;

	int res = cgi->doWork(request, response);

	if (file = cgi->file) return file->size();

	std::swap(out, cgi->out);

	size_t val = out.size();

	if (val <= 0 && res < 0) return res;

	return val;
}
int webx::ProcessBase::doWork(HttpRequest* request, HttpResponse* response)
{
	struct LoggerSetup
	{
		string logkey = LogThread::Instance()->getCurrentLogKey();

		~LoggerSetup()
		{
			LogThread::Instance()->setCurrentLogKey(logkey);
		}
		LoggerSetup(const string& key)
		{
			LogThread::Instance()->setCurrentLogKey(stdx::format("[%s][%s]", HttpServer::Instance()->getSequence().c_str(), key.c_str()));
		}
		void trace(const char* title, const char* msg, int len)
		{
			if (len == 0)
			{
				LogTrace(eTIP, "%s without message", title);
			}
			else if (len <= HTTP_GZIP_SIZE)
			{
				LogTrace(eTIP, "%s message[%s]", title, msg);
			}
			else
			{
				string tmp(msg, msg + HTTP_GZIP_SIZE);

				LogTrace(eTIP, "%s message[%s ...]", title, tmp.c_str());
			}
		}
	};

	int val = XG_SYSERR;
	sp<LoggerSetup> logger;

	if (request && this->request == NULL)
	{
		string key = CgiMapData::GetKey(request->getPath());

		if (key != "getcgilist" && key != "tracemodule")
		{
			logger = newsp<LoggerSetup>(key);

			key = request->getDataString();

			logger->trace("request", key.c_str(), key.length());
		}
	}

	this->request = request;
	this->response = response;

	try
	{
		val = func ? func() : process();
	}
	catch(const Exception& e)
	{
		int code = e.getErrorCode();
		const char* desc = e.getErrorString();
		const map<string, string>& fields = e.getFields();

		if (request && code == XG_NOTCHANGED)
		{
			string etag = request->getHeadValue("If-None-Match");

			if (etag.length() > 0)
			{
				response->setHeadValue("ETag", etag);
				response->setStatus(304);

				return clearResponse();
			}
		}

		LogTrace(eERR, "catch exception[%d][%s]", code, desc);

		response->setStatus(GetHttpStatus(code));

		if (fields.size() > 0)
		{
			JsonElement extra = json.addObject("extra");

			for (const auto& item : fields)
			{
				extra[item.first] = item.second;
			}
		}

		json["code"] = code;
		json["desc"] = desc;

		clearResponse();

		out << json;
	}
	catch(const exception& e)
	{
		response->setStatus(GetHttpStatus(XG_SYSERR));

		LogTrace(eERR, "catch exception[%s]", e.what());

		simpleResponse(XG_SYSERR);
	}
	catch(...)
	{
		response->setStatus(GetHttpStatus(XG_SYSERR));

		LogTrace(eERR, "catch unknown exception");

		simpleResponse(XG_SYSERR);
	}

	if (file)
	{
		if (logger)
		{
			string contype = response->getContentType();

			if (contype.find("text") == string::npos && contype.find("json") == string::npos)
			{
				string msg = stdx::format("binary:%s:%d", contype.c_str(), file->size());

				logger->trace("response", msg.c_str(), msg.length());
			}
			else if (response->getHeadValue("Content-Encoding") == "gzip")
			{
				string msg = stdx::format("gzip:%s:%d", contype.c_str(), file->size());

				logger->trace("response", msg.c_str(), msg.length());
			}
			else
			{
				logger->trace("response", file->getData(), file->size());
			}
		}

		return file->size();
	}

	if (val < 0 && out.empty()) return val;

	val = out.size();

	if (logger) logger->trace("response", out.str(), val);

	if (response && val > HTTP_GZIP_SIZE && createFile(val))
	{
		u_int32 len = val;

		if (GZIPCompress(out.str(), val, file->getData(), &len) == 0 && len < val)
		{
			response->setHeadValue("Content-Encoding", "gzip");
			file->truncate(len);
			out.clear();

			return len;
		}

		file = NULL;
	}

	return val;
}

string webx::GetCgiPathList()
{
	string path;
	static auto& appmap = *webx::GetAppMap();

	if (appmap.size() > 0)
	{
		for (auto& item : appmap) path += "|" + item.second.path;

		path = path.substr(1);
	}

	return path;
}

void webx::ReloadSystemConfig()
{
	typedef void (*ReloadFunc)();

	static ReloadFunc func = (ReloadFunc)Process::GetObject("HTTP_RELOAD_SYSTEM_CONFIG_FUNC");

	if (func) func();
}
	
map<string, webx::WebAppData>* webx::GetAppMap()
{
	static map<string, WebAppData> appmap;
	return &appmap;
}

sp<webx::ProcessBase> webx::GetWebApp(const string& path)
{
	string key = path;
	static auto& appmap = *GetAppMap();
	auto it = appmap.find(key == "/" ? "index" : stdx::tolower(key));

	return it == appmap.end() ? sp<webx::ProcessBase>() : it->second.create();
}

int webx::GetAccess(const initializer_list<const char*>& list, int index)
{
	if (index >= list.size()) return CGI_PROTECT;

	const char* val = list.begin()[index];

	if (val == NULL) return CGI_PRIVATE;

	string tmp = stdx::tolower(val);

	if (tmp == "public") return CGI_PUBLIC;
	if (tmp == "protect") return CGI_PROTECT;

	return CGI_PRIVATE;
}

const char* webx::GetString(const initializer_list<const char*>& list, int index)
{
	return index < list.size() ? list.begin()[index] : NULL;
}

bool webx::IsMailString(const string& str)
{
	if(str.length() < 4 || str.length() > 128) return false;
	
	size_t m = str.find('@');
	size_t n = str.rfind('.');
	
	if (m == string::npos || n == string::npos || m > n) return false;
	
	return true;
}

string webx::GetScriptString(const string& str)
{
	string msg = stdx::replace(str, "\\", "\\\\");

	msg = stdx::replace(msg, "\n", "\\n");
	msg = stdx::replace(msg, "\r", "\\r");
	msg = stdx::replace(msg, "\t", "\\t");
	msg = stdx::replace(msg, "\'", "\\\'");
	msg = stdx::replace(msg, "\"", "\\\"");

	return stdx::replace(msg, "</script>", "</'+'script>");
}

string webx::GetFileIcon(const string& filename)
{
	size_t pos = filename.rfind('.');
	static HttpServer* app = HttpServer::Instance();

	if (pos == string::npos) return "/res/img/file/ask.png";

	string url = "/res/img/file/";
	string ext = filename.substr(pos + 1);

	url += stdx::tolower(ext) + ".png";

	if (app->getCgiMapData(url).url.length() > 0) return url;

	if (ext == "png" || ext == "jpg" || ext == "gif" || ext == "bmp" || ext == "jpeg")
	{
		url = "/res/img/menu/image.png";
	}
	else if (ext == "h" || ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx")
	{
		url = "/res/img/file/cpp.png";
	}
	else if (ext == "zip" || ext == "rar" || ext == "tar" || ext == "gz" || ext == "7z")
	{
		url = "/res/img/file/zip.png";
	}
	else if (ext == "wav" || ext == "aac" || ext == "mid" || ext == "wma" || ext == "mp3")
	{
		url = "/res/img/file/wav.png";
	}
	else if (ext == "rmvb" || ext == "avi" || ext == "flv" || ext == "wmv" || ext == "mp4")
	{
		url = "/res/img/file/avi.png";
	}
	else if (ext == "exe" || ext == "dll" || ext == "lib" || ext == "apk" || ext == "so" || ext == "a")
	{
		url = "/res/img/file/exe.png";
	}
	else if (ext == "doc" || ext == "docx")
	{
		url = "/res/img/file/doc.png";
	}
	else if (ext == "xls" || ext == "xlsx")
	{
		url = "/res/img/file/xls.png";
	}
	else if (ext == "ppt" || ext == "pptx")
	{
		url = "/res/img/file/ppt.png";
	}
	else if (ext == "htm" || ext == "html")
	{
		url = "/res/img/file/htm.png";
	}
	else
	{
		url = "/res/img/file/ask.png";
	}

	if (app->getCgiMapData(url).url.length() > 0) return url;
	
	return "/res/img/file/ask.png";
}

int webx::PrintRecordview(StringCreator& out, const string& tabid)
{
	SmartBuffer content;
	static HttpServer* app = HttpServer::Instance();
	CgiMapData cfg = app->getCgiMapData("app/tableview/pub/recordview.htm");

	if (cfg.url.empty() || app->getFileContent(cfg.url, content) <= 0) return XG_SYSERR;

	string msg = stdx::replace(content.str(), "${recordoperhtml}", webx::GetScriptString(out.toString()));

	out.setContent(stdx::replace(msg, "${tabid}", tabid));

	return out.size();
}

string webx::GetLimitString(DBConnect* dbconn, int pagesize, int page)
{
	string str = dbconn->getSystemName();
	
	if (str == "SQLite")
	{
		stdx::format(str, " LIMIT %d OFFSET %d", pagesize, page * pagesize);
	}
	else if (str == "MySQL")
	{
		stdx::format(str, " LIMIT %d,%d", page * pagesize, pagesize);
	}
	else
	{
		str = stdx::EmptyString();
	}
	
	return str;
}

bool webx::IsFileName(const string& str, int minlen, int maxlen)
{
	int len = str.length();
	const char* errstr = "`'^*<>,:;?|%\r\n\t\"\\/";

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	--len;

	for (int i = 0; i < len; i++)
	{
		CHECK_FALSE_RETURN(strchr(errstr, str[i]) == NULL);
	}
	
	return str.find("..") == string::npos;
}

bool webx::IsFilePath(const string& str, int minlen, int maxlen)
{
	int len = str.length();
	const char* errstr = "`'^*<>,:;?|%\r\n\t\"";

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	const char* end = str.c_str();

	for (int i = 0; i < len; i++)
	{
		CHECK_FALSE_RETURN(strchr(errstr, str[i]) == NULL);
	}

	return str.find("..") == string::npos;
}

bool webx::IsAlnumString(const string& str, int minlen, int maxlen)
{
	int len = str.length();

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	return ::IsAlnumString(str.c_str());
}

bool webx::IsSeqnoString(const string& str, int minlen, int maxlen)
{
	return IsAlnumString(str, minlen, maxlen);
}

void webx::CheckFileName(const string& str, int minlen, int maxlen)
{
	if (!IsFileName(str, minlen, maxlen)) throw Exception(XG_PARAMERR);
}

void webx::CheckFilePath(const string& str, int minlen, int maxlen)
{
	if (!IsFilePath(str, minlen, maxlen)) throw Exception(XG_PARAMERR);
}

void webx::CheckAlnumString(const string& str, int minlen, int maxlen)
{
	if (!IsAlnumString(str, minlen, maxlen)) throw Exception(XG_PARAMERR);
}

void webx::CheckSeqnoString(const string& str, int minlen, int maxlen)
{
	CheckAlnumString(str, minlen, maxlen);
}

int webx::LoadHttpPlugin(const string& path)
{
	int val;
	sp<DllFile> dll = DllFile::Get(path);
	HttpPluginInitFunc HttpPluginInit = NULL;

	if (!dll)
	{
		LogTrace(eERR, "load plugin[%s] failed", path.c_str());

		return XG_FAIL;
	}
	
	if (dll->read(HttpPluginInit, "HttpPluginInit"))
	{
		val = HttpPluginInit(HttpServer::Instance());
	}
	else
	{
		val = XG_SYSERR;
	}
	
	if (val < 0)
	{
		LogTrace(eERR, "initialize plugin[%s] failed[%d]", path.c_str(), val);
	}
	else
	{
		LogTrace(eIMP, "initialize plugin[%s] success", path.c_str());
	}
	
	return val;
}

int webx::SaveParamItem(FileParamItem& item, const string& path)
{
	if (item.len < 0) return item.len;

	if (item.filename.empty())
	{
		item.filepath = string(item.data, item.data + item.len);
			
		return item.len;
	}
	else
	{
		size_t pos;
		string extnm;

		if ((pos = item.filename.rfind('.')) == string::npos)
		{
			extnm = ".dat";
		}
		else
		{
			if ((extnm = item.filename.substr(pos)).length() <= 1) extnm = ".dat";
		}

		while (true)
		{
			item.filepath = path + "/dat/" + DateTime::GetBizId() + extnm;
			item.filepath = stdx::replace(item.filepath, "//", "/");
			
			if (path::type(item.filepath) <= eNONE) break;

			Sleep(1);
		}

		XFile file;

		if (file.create(item.filepath))
		{
			return item.len > 0 ? file.write(item.data, item.len) : item.len;
		}
	}

	return XG_SYSERR;
}

int webx::GetFileParamList(vector<FileParamItem>& vec, SmartBuffer& buffer, HttpServer* app, HttpRequest* request, HttpResponse* response)
{
	int sz = request->getDataSize();
	sp<Socket> sock = response->getSocket();
	string boundary = request->getBoundary();
	SmartBuffer content = request->getContent();
	int readed = content.size() - request->getHeadSize();
	
	if (readed < 0 || sz <= 0 || readed > sz || sz > XG_UPLOADFILE_REQUEST_MAXSZ || boundary.empty()) return XG_DATAERR;

	memmove(content.str(), content.str() + request->getHeadSize(), readed);
	content.truncate(readed);

	int len = 0;
	int timeout = app->getTimeout();

	if (readed == sz)
	{
		buffer = content;
	}
	else
	{
		time_t utime = time(NULL);

		memcpy(buffer.malloc(sz), content.str(), readed);

		while (readed < sz)
		{
			if ((len = sock->read(buffer.str() + readed, buffer.size() - readed, false)) < 0) return len;

			if (len < SOCKET_TIMEOUT_LIMITSIZE)
			{
				if (utime + timeout < time(NULL)) return XG_TIMEOUT;

				Sleep(10);
			}

			utime = time(NULL);

			readed += len;
		}
	}

	int res;
	string msg;
	ContentNode node;
	FileParamItem item;
	const char* end = NULL;
	const char* endtag = "\r\n";
	string tag = "--" + boundary;
	const char* str = buffer.str();
	const size_t endtaglen = strlen(endtag);

	if (memcmp(str, tag.c_str(), tag.length())) return XG_DATAERR;

	str += tag.length() + endtaglen;

	auto getTrimPairString = [](const string& str, char ch){
		return (str.length() >= 2 && str[0] == ch && str.back() == ch) ? str.substr(1, str.length() - 2) : str;
	};
	
	while (true)
	{
		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), endtag, endtag + endtaglen);

		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		node.setEndSpliter(";");
		node.setKeySpliter("=");
		msg = stdx::replace(string(str, end), " ", "");
		
		if (msg.empty()) return XG_DATAERR;

		node.parse(msg.c_str());

		item.key = getTrimPairString(node.getValue("name"), '\"');

		if (item.key.empty()) return XG_DATAERR;

		item.filename = getTrimPairString(node.getValue("filename"), '\"');
		str = end + endtaglen;

		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), endtag, endtag + endtaglen);

		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		node.setEndSpliter(";");
		node.setKeySpliter(":");
		msg = stdx::replace(string(str, end), " ", "");

		if (msg.empty())
		{
			item.contype.clear();
		}
		else
		{
			node.parse(msg.c_str());
			item.contype = node.getValue("Content-Type");

			if (item.contype.empty()) item.contype = "text/plain";
		}
		
		item.data = str = end + endtaglen;

		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), tag.c_str(), tag.c_str() + tag.length());
		
		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		if (item.contype.empty())
		{
			item.len = (end - str) - 2;
		}
		else
		{
			item.len = (end - str) - 4;
			item.data += endtaglen;
		}

		if ((res = SaveParamItem(item, app->getPath())) < 0)
		{
			if (item.filename.length() > 0) LogTrace(eIMP, "recv file[" + item.filename + "] failed");

			return res;
		}
		
		if (item.filename.length() > 0)
		{
			LogTrace(eINF, "recv file[" + item.filename + "] success");

			vec.push_back(item);
		}

		str = end + tag.length();
		
		if (str + endtaglen >= (const char*)(buffer.str()) + buffer.size()) break;

		if (memcmp(str, endtag, endtaglen)) break;

		str += endtaglen;
	}

	return vec.size();
}

int webx::PackJson(sp<QueryResult> rs, JsonElement& json)
{
	return PackJson(rs, json, [](int idx, const string& data){
		return data;
	});
}

int webx::PackJson(sp<QueryResult> rs, const string& name, JsonElement& json)
{
	return PackJson(rs, name, json, [](int idx, const string& data){
		return data;
	});
}

int webx::PackJson(sp<QueryResult> rs, JsonElement& json, function<string(int, const string&)> func)
{
	if (!rs) return XG_ERROR;

	int sz = 0;
	sp<RowData> row;
	vector<string> vec;
	int colsz = rs->cols();
	
	if (colsz <= 0) return XG_ERROR;

	for (int i = 0; i < colsz; i++)
	{
		string name = rs->getColumnName(i);
		vec.push_back(stdx::tolower(name));
	}

	if (row = rs->next())
	{
		for (int i = 0; i < colsz; i++)
		{
			json[vec[i]] = func(i, row->getString(i));
		}
	}

	return sz;
}

int webx::PackJson(sp<QueryResult> rs, const string& name, JsonElement& json, function<string(int, const string&)> func)
{
	if (!rs) return XG_ERROR;

	int sz = 0;
	sp<RowData> row;
	vector<string> vec;
	int colsz = rs->cols();
	JsonElement list = json.addArray(name);

	if (list.isArray()) sz = list.size();

	if (colsz <= 0) return XG_ERROR;

	for (int i = 0; i < colsz; i++)
	{
		string name = rs->getColumnName(i);
		vec.push_back(stdx::tolower(name));
	}

	while (row = rs->next())
	{
		JsonElement item = list[sz++];

		for (int i = 0; i < colsz; i++)
		{
			item[vec[i]] = func(i, row->getString(i));
		}
	}

	return sz;
}
///////////////////////////////////////////////////////////
#endif
